package org.smartboot.compare.comparator;

import org.smartboot.compare.ComparatorContext;
import org.smartboot.compare.constants.CompareConstants;
import org.smartboot.compare.difference.Difference;
import org.smartboot.compare.utils.ComparatorUtils;

import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * @author qinluo
 * @version 1.0.0
 * @since 2019-05-27 17:06
 */
public abstract class AbstractComparator<T> implements Comparator<T> {

    /**
     * Actually Generic Type.
     */
    protected final Class<?> type;

    static class GroupLevel {
        /**
         * 某一层实际类型
         *
         * 例如{@code
         * List<T>}中的List.class
         */
        private Class<?> rawType;

        /**
         * 某一层中的实际泛型列表
         *
         * 例如 {@code Triple<Integer, Integer, K>}中的<code>Integer, Integer</code>
         */
        private final List<Type> actualTypes = new ArrayList<>();

        /**
         * 某一层中的泛型类型变量列表 例如 {@code Triple<Integer, Integer, K>}K
         */
        private final List<TypeVariable<?>> typeParameters = new ArrayList<>();
    }

    public AbstractComparator() {
        this.type = parseType();
    }

    public AbstractComparator(Object unused) {
        /* Does not parse */
        this.type = Object.class;
    }

    /**
     * Parse Actual type.
     */
    private Class<?> parseType() {
        Class<?> expect = this.getClass();
        List<GroupLevel> levels = new ArrayList<>();
        while (expect != AbstractComparator.class) {
            Type genericSuperclass = expect.getGenericSuperclass();
            if (!(genericSuperclass instanceof ParameterizedType)) {
                expect = expect.getSuperclass();
                continue;
            }

            GroupLevel level = new GroupLevel();

            Type rawType = ((ParameterizedType) genericSuperclass).getRawType();
            if (rawType instanceof Class) {
                level.rawType = (Class<?>) rawType;
            }

            ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            level.actualTypes.addAll(Arrays.asList(actualTypeArguments));
            level.typeParameters.addAll(Arrays.asList(level.rawType.getTypeParameters()));

            levels.add(0, level);
            expect = expect.getSuperclass();
        }

        // Sanity check.
        assert levels.size() >= 1;

        boolean matched = false;
        Type candidate = Object.class;
        GroupLevel top = levels.get(0);
        Type candidateGeneric = top.actualTypes.get(0);
        /*
           Case: class GenericClass extends AbstractComparator<List<?>>
         */
        if (candidateGeneric instanceof ParameterizedType) {
            Type rawType = ((ParameterizedType) candidateGeneric).getRawType();
            if (rawType instanceof Class) {
                candidate = rawType;
                matched = true;
            }
        }

        /*
           Case: class GenericArray extends AbstractComparator<T[]>
         */
        boolean arrayGeneric = false;
        int dimension = 0;
        while (candidateGeneric instanceof GenericArrayType) {
            dimension++;
            arrayGeneric = true;
            candidateGeneric = ((GenericArrayType)candidateGeneric).getGenericComponentType();
        }

        /*
           Case: class DirectSubclass extends AbstractComparator<Long>
         */
        if (candidateGeneric instanceof Class) {
            candidate = candidateGeneric;
            matched = true;
        } else {
            for (int i = 1; i < levels.size(); i++) {
                GroupLevel level = levels.get(i);
                // find same candidate with candidateGeneric
                int n = -1;
                boolean foundn = false;
                for (Type typeParameter : level.typeParameters) {
                    n++;
                    if (Objects.equals(typeParameter.getTypeName(), candidateGeneric.getTypeName())) {
                        foundn = true;
                        break;
                    }
                }

                // foundn, check index level.actualTypes[n] is instanceof class.
                /*
                 * Compatible:
                 * class A<T, R> extends AbstractComparer<T> {}
                 * class B<T> extends A<String, T> {}
                 * class C extends B<Integer> {}
                 *
                 */
                if (foundn) {
                    Type pos = level.actualTypes.get(n);
                    if (pos instanceof ParameterizedType) {
                        Type rawType = ((ParameterizedType) pos).getRawType();
                        if (rawType instanceof Class) {
                            candidate = rawType;
                            matched = true;
                        }
                    }

                    if (pos instanceof Class) {
                        candidate = pos;
                        matched = true;
                        break;
                    }

                    Type g0 = pos;
                    while (g0 instanceof GenericArrayType) {
                        dimension++;
                        arrayGeneric = true;
                        g0 = ((GenericArrayType)g0).getGenericComponentType();
                    }
                    candidateGeneric = g0;
                }
            }
        }

        if (!matched) {
            throw new IllegalStateException("Unrealized type in subclass!!!");
        }

        if (arrayGeneric) {
            Class<?> rawType = (Class<?>) candidate;
            rawType = Array.newInstance(rawType, new int[dimension]).getClass();
            candidate = rawType;
        }

        return (Class<?>) candidate;
    }

    /**
     * 比较器对应的Java类型
     *
     * @return Java class type.
     */
    public Class<?> getType() {
        return type;
    }

    @Override
    public Difference compare(ComparatorContext<T> context) {
        // avoid stack overflow.
        if (context.getDepth() > CompareConstants.MAX_COMP_DEPTH) {
            return Difference.SAME;
        }

        //2个对象都为空的时候, 即使对象实际类型不一致 也认为相等
        if (ComparatorUtils.checkAllNull(context.getExpect(), context.getActual())) {
            return Difference.SAME;
        }

        T expect = (T)this.type.cast(context.getExpect());
        T actual = (T)this.type.cast(context.getActual());

        Difference difference = compare(expect, actual, context);

        // Attach source.
        if (difference != null && difference.getSource() == null) {
            difference.attachSource(this);
        }

        return difference;
    }

    /**
     * 比较2个对象是否相等
     * @param expect    比较基准对象
     * @param actual    待比较对象
     * @param context   比较上下文，承载比较结果
     */
    public abstract Difference compare(T expect, T actual, ComparatorContext<T> context);

    /**
     * Check current compare is recycled.
     *
     * @param context compare context.
     * @param expect  expect.
     * @param actual  actual
     * @return        is recycled.
     */
    protected boolean checkRecycled(ComparatorContext<?> context, Object expect, Object actual, boolean add) {
        if (context.getRecycleChecker().isRecycle(expect, actual, context.getDepth())) {
            return true;
        }

        if (add) {
            context.getRecycleChecker().addRecycle(expect, actual, context.getDepth());
        }

        return false;
    }
}
